package com.zhny.sensor.nio;

import com.zhny.sensor.entity.*;
import com.zhny.sensor.service.*;
import com.zhny.sensor.utils.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Auther: zhy
 * @Date: 2019/11/14
 * @Description: 设备功能服务
 */
@Service
@ChannelHandler.Sharable // 可以被多次调用，被装配到多个pipline中去
public class ServerHandler extends ChannelInboundHandlerAdapter {

    @Autowired
    private EntrustService entrustService;
    @Autowired
    private UsmsService usmsService;

    @Autowired
    private UserService userService;

    @Autowired
    private IdWorker idWorker;

    @Autowired
    private BarcodeMachineService barcodeMachineService;

    @Autowired
    private BarcodeMachineUserService barcodeMachineUserService;

    @Autowired
    private PoultryCategoryService poultryCategoryService;

    @Autowired
    private SlaughterHouseService slaughterHouseService;

    @Autowired
    private SlaughterHousePoultryService slaughterHousePoultryService;

    @Autowired
    private SlaughterHouseRepairService slaughterHouseRepairService;

    @Autowired
    private BarcodeMachineStatusService barcodeMachineStatusService;

    @Autowired
    private BarcodeMachineAlertservice barcodeMachineAlertservice;

    @Autowired
    private LabelingRecordService labelingRecordService;

    @Autowired
    private ProgramVersionService programVersionService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisUtil redisUtil;


    @Value("${firmware.filepath}")
    private String firmwareFilePath;

    @Value("${datareport.cycle}")
    private Byte dataReportCycle;

    @Value("${connection.limits}")
    private int CONNECTIONLIMITS;

    private static final Logger log = LoggerFactory.getLogger(ServerHandler.class);


    private static AtomicInteger connections = new AtomicInteger(0);


    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
        connections.getAndDecrement();
        log.debug(Thread.currentThread().getId() + "当前的连接数为:" + connections.get());
    }

    //当一个新的连接已经被建立时，该方法将会被调用
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String ip = insocket.getAddress().getHostAddress();
        int port = insocket.getPort();
        log.info(Thread.currentThread().getId() + "已经连接，连接的ip为  " + ip + "," + port);
        if (connections.getAndAdd(1) >= CONNECTIONLIMITS) {//线程问题
            ctx.close();
            log.error(Thread.currentThread().getId() + "因为连接数已满，断开了这个连接");
            return;
        }
        log.debug(Thread.currentThread().getId() + "当前的连接数为:" + connections.get());
        super.channelActive(ctx);
    }

    /**
     * 服务端接受到客户端数据后被调用
     * 待完善的地方：可以自定义一个线程池，用来处理业务的逻辑
     *
     * @param ctx
     * @param msg 客户端数据 ByteBuf
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 读取数据
        ByteBuf dataBuf = (ByteBuf) msg;
        int len = dataBuf.readableBytes();
        byte[] dataByte = new byte[len];
        dataBuf.getBytes(dataBuf.readerIndex(), dataByte);

        log.info(Thread.currentThread().getId() + " 收到数据的长度为" + len);
        log.info(Thread.currentThread().getId() + " 接收数据为:[" + ByteUtils.bytesToHexString(dataByte) + "fc fc]");

        //ByteBufUtil.hexDump(dataBuf);  用这个速度是会比较快点，不用复制两次，但这个格式不喜欢


        // 获取帧代号
        int flag = Integer.parseInt(ByteUtils.byteToHex(dataBuf.getByte(4)), 16);
        log.debug(Thread.currentThread().getId() + "测试-----flag  " + flag);

        // CRC校验，校验不通过直接返回,这里跳过了指令7，为了指令7出现校验码错误也会返回数据
        if (!checkCRCVal(dataByte) && flag != 0x07) {
            return;
        }

        //校验完要再验证数据是否符合协议格式，不符合就丢弃。
        //每个报错都写进日志里


        // 获取设备编号（脚标机编号）
        byte[] deviceIdByte = new byte[6];
        dataBuf.getBytes(5, deviceIdByte);
//        String deviceId = Integer.parseInt(ByteBufUtil.hexDump(deviceIdByte),16 ) + "";
//        String deviceId = ByteUtils.bytesToASCIIString(deviceIdByte);
        String deviceId = ByteUtils.bytesToAddress(deviceIdByte);

        log.debug(Thread.currentThread().getId() + "测试-----设备编号  " + deviceId);

        //web端在添加设备的时候BarcodeMachine表的id字段和number字段要相同
        // 拿到脚标机的详细信息
        Optional<BarcodeMachine> optionalBarcodeMachine = barcodeMachineService.findByNumber(deviceId);

        //这里是如果查不到设备的信息就直接响应客户端一段数据
        if (!optionalBarcodeMachine.isPresent()) {      //isPresent()方法用于判断包装对象的值是否非空
            // 响应内容：日期
            LocalDateTime date = LocalDateTime.now();
            String dateHex = DateUtils.datetoHex(date);

            byte[] responseData = ByteUtils.hexStringToBytes(dateHex);
            ByteBuf responseBuf = buildLoginResponsePackage(deviceIdByte, (byte) 0x01, new byte[]{(byte) 0x12, 0x00}, responseData);
            ctx.writeAndFlush(responseBuf);

            log.error(Thread.currentThread().getId() + "该设备未被注册！设备编号：{}", deviceId);
            return;
        }

        //脚标机设备
        BarcodeMachine barcodeMachine = optionalBarcodeMachine.get();

        // 设备请求连接
        if (flag == 0x01) {
            log.info(Thread.currentThread().getId() + "Option：设备请求连接，设备编号：{}", deviceId);

            // 响应内容：日期
            LocalDateTime date = LocalDateTime.now();
            String dateHex = DateUtils.datetoHex(date);

            byte[] responseData = ByteUtils.hexStringToBytes(dateHex);

            //获取设备的时间版本编号
            byte[] timeByte = new byte[4];
            dataBuf.getBytes(13, timeByte);
            String timeVersion = ByteUtils.bytesToAddress(timeByte);
            log.debug("脚标机的时间版本编号为：" + timeVersion);

            //先查出当前脚标机的用户信息版本与禽类信息版本
            String userMsgVersion = barcodeMachine.getUserMsgVersion();
            String poultryMsgVersion = barcodeMachine.getPoultryMsgVersion();

            //判断信息版本是否需要更新 minxin
            byte[] updateCode = new byte[]{0x00, 0x00};
            //这里需要添加一个标识的符号，如果同时需要更新用户和禽类信息的话 updateCode[1] = 0x03;
            int biaozhi = 0;
            // 用户信息更新请求
            if (!timeVersion.equals(userMsgVersion)) {
                updateCode[0] = 0x00;
                updateCode[1] = 0x01;
                log.info(Thread.currentThread().getId() + " 用户信息更新请求");
                biaozhi++;
            }
            // 禽类品种信息更新请求
            if (!timeVersion.equals(poultryMsgVersion)) {
                updateCode[0] = 0x00;
                updateCode[1] = 0x02;
                log.info(Thread.currentThread().getId() + " 禽类品种信息更新请求");
                biaozhi++;
            }
            //证明同时需要更新用户和禽类信息
            if (biaozhi == 2) {
                updateCode[1] = 0x03;
                log.info(Thread.currentThread().getId() + " 同时有用户信息更新和家禽信息更新");
            }

            //这里需要增加一个功能，机器首次连接服务器之后，查询上次订单中正在贴标的将其改为完成
            entrustService.updateEntrustState(5, deviceId, 4);

            //将脚标机的经纬度存入数据库
//            byte[] longitude = new byte[10]; //经度
//            byte[] latitude = new byte[10];  //纬度
//            dataBuf.getBytes(17,longitude);
//            dataBuf.getBytes(27,latitude);
//            double longitudeFromByte = GPSUtils.getLongitudeFromByte(longitude);
//            double latitudeFromByte = GPSUtils.getLongitudeFromByte(latitude);
//            barcodeMachine.setLongitude(longitudeFromByte+"");
//            barcodeMachine.setLatitude("0"+latitudeFromByte);

            //=========这里用来判断脚标机的经纬度,不在指定的位置就退出===================================
//            byte[] longitude = new byte[10];
//            byte[] latitude = new byte[10];
//            dataBuf.getBytes(17,longitude);
//            dataBuf.getBytes(27,latitude);
//            if(!checkGPS(barcodeMachine, longitude, latitude)){
//                updateCode[0] = 0x11;  //表示设备使用位置不正确，GPS位置不正确
//            }
            //========================================================================================
            //指令成功顺便存入redis
            addMessage(deviceId, "0");

            ByteBuf responseBuf = buildLoginResponsePackage(deviceIdByte, (byte) 0x01, updateCode, responseData);
            ctx.writeAndFlush(responseBuf);
            return;

//
//            // 校验经纬度
//            byte[] longitude = new byte[10];
//            byte[] latitude = new byte[10];
//            dataBuf.getBytes(17,longitude);
//            dataBuf.getBytes(27,latitude);
//
//
//            //1111111这里还欠个登录时检测到设备被禁用的情况
////            if (checkGPS(barcodeMachine, longitude, latitude)) {
//            if (true) {
//
//                byte[] updateCode = new byte[]{0x00,0x00};
//
//                //更新信息如果没有用时间版本管理，将会导致更多管理代价，比如你用isupdate判断后，下次再开机，他还是发现
//                //这个isupdate还是1，然后还是更新。这里主要的难度是，一台设备更新了，但其他设备没有，
//                //所以isupdate还是不能改，只有记录了所有设备都更新了，isupdate才改。
//                //需要比较更新的时间版本
//                //但其实只要记录版本号就可以啊 后期交给你们维护改吧
//
//
//                //1111111111  web端改更新状态
//                // 用户信息更新请求
//                if (hasNewUser(deviceId)) {
//                    updateCode[0] = 0x00;
//                    updateCode[1] = 0x01;
//                    log.info(Thread.currentThread().getId()+"用户信息更新请求");
//                }
//
//                //1111111111
//                // 禽类品种信息更新请求
//                if (hasNewPoultry(barcodeMachine.getSlaughterHouseId())) {
//                    updateCode[0] = 0x00;
//                    updateCode[1] = 0x02;
//                    log.info(Thread.currentThread().getId()+"禽类品种信息更新请求");
//                }
//
//                //1111111111
//                // 固件更新请求
//                int firmwareVersion = dataBuf.getByte(11);
//                if (hasNewFirmware(firmwareVersion)) {
//                    updateCode[0] = 0x00;
//                    updateCode[1] = 0x04;
//                    log.info(Thread.currentThread().getId()+"固件更新请求");
//                }
//
//                byte[] responseData = ByteUtils.hexStringToBytes(dateHex);
//                ByteBuf responseBuf = buildLoginResponsePackage(deviceIdByte,(byte) 0x01,updateCode,responseData);
//                ctx.writeAndFlush(responseBuf);
//
//                log.info(Thread.currentThread().getId()+"登录成功! 设备编号：{}",deviceId);
//
//            } else {
//
//                byte[] responseData = ByteUtils.hexStringToBytes(dateHex);
//                ByteBuf responseBuf = buildLoginResponsePackage(deviceIdByte,(byte) 0x01,new byte[]{(byte) 0x11,0x00},responseData);
//                ctx.writeAndFlush(responseBuf);
//
//               log.error(Thread.currentThread().getId()+"登录失败！经纬度验证不通过，超出使用范围！");
//
//            }

        } else if (flag == 0x02) {  // 用户信息更新，这里我统一返回静态二维码给脚标机

            log.info(Thread.currentThread().getId() + " 用户信息更新，设备编号：{}", deviceId);

            //这里需要将普通用户、管理员、维修人员的静态二维码查询出来，封装以后响应给脚标机
            //1、查询普通用户的静态二维码，通过设备号
            //String userStaticCode = userService.findById(barcodeMachine.getUserId()).getStaticDecode();
            //2、查询管理员的静态二维码
            String adminStaticcode = userService.findById(slaughterHouseService.findById(Integer.valueOf(barcodeMachine.getSlaughterHouseId())).getUserId()).getStaticDecode();
            //3、查询维修人员的静态二维码
//            List<SlaughterHouseRepair> repairs = slaughterHouseRepairService.findByHouseId(barcodeMachine.getSlaughterHouseId());
//            Set<String> repairsCodeSet = new HashSet<>();
//            for (SlaughterHouseRepair s : repairs) {
//                repairsCodeSet.add(userService.findById(s.getUserId()).getStaticDecode());
//            }
//            Object[] repairsCode = repairsCodeSet.toArray();

            //将静态码封装成为字节数据
            List<byte[]> userInfoList = parseUserInfoTobytes(adminStaticcode, null, null);

            //指令成功顺便存入redis
            addMessage(deviceId, "1");
            // 创建用户更新信息包
            ByteBuf responseBuf = buildUpdateResponsePackage(deviceIdByte, (byte) 0x02, (short) 0, userInfoList);
            ctx.writeAndFlush(responseBuf);


        } else if (flag == 0x03) {  // 禽类信息更新

            log.info(Thread.currentThread().getId() + "Option：禽类信息更新，设备编号：{}", deviceId);
            // 获取禽类品种信息
            //通过屠宰场id查出禽类
            //List<PoultryCategory> poultryCategoryList = poultryCategoryService.getPoultryCategoryBySlaughterHouseId(barcodeMachine.getSlaughterHouseId());
            List<PoultryCategory> poultryCategoryList = poultryCategoryService.getPoultryCategoryByStallsId(barcodeMachine.getStallsId());

            for (PoultryCategory p : poultryCategoryList) {
                log.debug(Thread.currentThread().getId() + "更新的禽类信息" + p.getName());
            }
            //指令成功顺便存入redis
            addMessage(deviceId, "2");
            //将禽类信息封装成功字节数组
            List<byte[]> poultryCategoryBytesList = parsePoultryCategoryTobytes(poultryCategoryList);

            // 创建禽类品种信息更新数据包
            ByteBuf responseBuf = buildUpdateResponsePackage(deviceIdByte, (byte) 0x03, (short) 0, poultryCategoryBytesList);
            System.out.println(responseBuf);
            ctx.writeAndFlush(responseBuf);

//        } else if (flag == 0x04) { // 设备程序更新信息
//
//            log.info(Thread.currentThread().getId() + "Option：设备程序更新信息，设备编号：{}", deviceId);
//
//            // 获取文件更新信息
//            int fileSize = FileUtils.getFileSize(firmwareFilePath);
//            if (fileSize == 0) {
//                log.error(Thread.currentThread().getId() + "要更新的程序文件不存在或有损坏！请上传文件......");
//            }
//
//            int packageLength = (int) Math.ceil(fileSize / 1024);
//            ProgramVersion programVersion = programVersionService.getProgramVersion();
//            int firmwareVersion = programVersion.getFirmwareVersion();
//            byte firmwareVersionByte = (byte) firmwareVersion;
//            byte[] packageLengthBytes = ByteUtils.intToBytes(packageLength);
//            byte[] fileSizeBytes = ByteUtils.intToBytes(fileSize);
//
//            // 创建程序更新数据包
//            ByteBuf responseBuf = buildProgramUpdateResponsePackage(deviceIdByte, (byte) 0x04, firmwareVersionByte, packageLengthBytes, fileSizeBytes);
//            ctx.writeAndFlush(responseBuf);

        } else if (flag == 0x05) { // 用户请求登陆
            log.info(Thread.currentThread().getId() + "Option：登陆请求，设备编号：{}", deviceId);
//            byte[] barcodeNumber = new byte[20];
//            dataBuf.getBytes(11, barcodeNumber);
            byte[] barcodeNumber = new byte[20];
            dataBuf.getBytes(11, barcodeNumber);
            ByteBuf byteBuf;
            //String barcode = ByteUtils.bytesToASCIIString(barcodeNumber);

            String barcode = ByteUtils.bytesToASCIIString(barcodeNumber);
            log.debug(Thread.currentThread().getId() + "获取得到的二维码为   " + barcode);

            String value = (String) redisUtil.get(barcode);  //获取value
            if (value == null) {
                log.error("空用户,非法二维码，登陆失败！");
                byteBuf = buildHeartbeatResponsePackage(deviceIdByte, (byte) 0x05, new byte[]{0x00});
            } else {
                String[] strs = value.split("_");
                byte[] data = new byte[21];
                Arrays.fill(data, (byte) 0x00);
                if (!strs[0].equals(deviceId)) {
                    log.error("空用户,脚标机编号有误，登陆失败！" + strs[0]);
                    byteBuf = buildHeartbeatResponsePackage(deviceIdByte, (byte) 0x05, data);
                } else {
                    if (strs[1].equals("1")) {
                        //指令成功顺便存入redis
                        addMessage(deviceId, "3");
                        log.info("档口用户登陆成功！");
                        data[0] = 0x01;
                        byteBuf = buildHeartbeatResponsePackage(deviceIdByte, (byte) 0x05, data);
                    } else if (strs[1].equals("2")) {
                        //指令成功顺便存入redis
                        addMessage(deviceId, "3");
                        log.info("屠宰场管理员登陆成功！");
                        data[0] = 0x02;
                        byteBuf = buildHeartbeatResponsePackage(deviceIdByte, (byte) 0x05, data);
                    } else if (strs[1].equals("3")) {
                        //指令成功顺便存入redis
                        addMessage(deviceId, "3");
                        log.info("维修人员登陆成功！");
                        data[0] = 0x03;
                        byteBuf = buildHeartbeatResponsePackage(deviceIdByte, (byte) 0x05, data);
                    } else {
                        //指令成功顺便存入redis
                        addMessage(deviceId, "8");
                        log.info("去向市场客户登陆成功！");
                        //这里需要带上客户的名字返回
                        //根据订单id查询客户名称
                        String orderId = strs[1];
                        String entrustName = entrustService.findById(orderId).getName();
                        data[0] = 0x04;
                        byte[] entrustNameGbk = entrustName.getBytes(Charset.forName("GBK"));
                        System.arraycopy(entrustNameGbk, 0, data, 1, entrustNameGbk.length);
                        byteBuf = buildHeartbeatResponsePackage(deviceIdByte, (byte) 0x05, data);
                    }
                }
            }
            // 响应设备数据
            ctx.writeAndFlush(byteBuf);

        } else if (flag == 0x06) { // 设备心跳数据

            log.info(Thread.currentThread().getId() + "Option：设备心跳数据，设备编号：{}", deviceId);

            // 获取设备状态与条码剩余量
            byte deviceStatus = dataBuf.getByte(11);
            short barcodeSurplusByte = dataBuf.getShort(12);

            log.debug(Thread.currentThread().getId() + "设备状态   " + deviceStatus);
            log.debug(Thread.currentThread().getId() + "条码剩余量   " + barcodeSurplusByte);

            // 保存设备状态信息 在线状态（离线：0、正常：1、卡纸：2、缺纸：3）
            if (deviceStatus == 0x00) { // 无故障，正常
                barcodeMachineStatusService.updateBarcodeMachineStatus(deviceId, "1", (int) barcodeSurplusByte);
            } else if (deviceStatus == 0x23) {  // 卡纸
                //指令成功顺便存入redis
                addMessage(deviceId, "4");
                barcodeMachineStatusService.updateBarcodeMachineStatus(deviceId, "2", (int) barcodeSurplusByte);
                // 报警信息
                barcodeMachineAlertservice.addBarcodeMachineAlert("卡纸", deviceId, barcodeMachine.getCreateBy());
                //发送短信提醒用户
                //首先查看redis数据库中是否存在  (key,value)——>(脚标机id.故障状态)
                //"usms_" 这里加个前缀是为了redis的key不与实时指令监控那边的redis key 冲突
                String status = (String) redisTemplate.opsForValue().get("usms_" + barcodeMachine.getNumber());
                if (status == null || status == "缺纸") {
                    //如果没有key值，则插入一条数据，设置过期时间为1小时，随后发送短信提醒用户
                    //如果查到了该key值，但是状态跟当前故障状态不同（比如卡纸变成缺纸），则更新数据库的信息，随后发送短信提醒用户
                    redisUtil.set("usms_" + barcodeMachine.getNumber(), "卡纸", 3600L);
                    //通过设备id找到BarcodeMachineUser的维修人员卡号，再通过卡号去找到维修人员的手机号
                    User user = userService.findByIdCard(barcodeMachineUserService.getBarcodeMachineUser(deviceId).getMaintenanceId());
                    String phone = user.getPhone();
                    if (phone != null)
                        usmsService.sendMessage(phone, barcodeMachine.getStallsId(), barcodeMachine.getNumber(), "卡纸");
                }
                //其他情况则无需做任何操作

            } else if (deviceStatus == 0x24) { // 缺纸
                //指令成功顺便存入redis
                addMessage(deviceId, "5");
                barcodeMachineStatusService.updateBarcodeMachineStatus(deviceId, "3", (int) barcodeSurplusByte);

                // 报警信息
                barcodeMachineAlertservice.addBarcodeMachineAlert("缺纸", deviceId, barcodeMachine.getCreateBy());
                //发送短信提醒用户
                //首先查看redis数据库中是否存在  (key,value)——>(脚标机id.故障状态)
                String status = (String) redisTemplate.opsForValue().get("usms_" + barcodeMachine.getNumber());
                if (status == null || status == "卡纸") {
                    //如果没有key值，则插入一条数据，设置过期时间为1小时，随后发送短信提醒用户
                    //如果查到了该key值，但是状态跟当前故障状态不同（比如卡纸变成缺纸），则更新数据库的信息，随后发送短信提醒用户
                    redisUtil.set("usms_" + barcodeMachine.getNumber(), "缺纸", 3600L);
                    //通过设备id找到BarcodeMachineUser的维修人员卡号，再通过卡号去找到维修人员的手机号
                    User user = userService.findByIdCard(barcodeMachineUserService.getBarcodeMachineUser(deviceId).getMaintenanceId());
                    String phone = user.getPhone();
                    if (phone != null)
                        usmsService.sendMessage(phone, barcodeMachine.getStallsId(), barcodeMachine.getNumber(), "缺纸");
                }
                //其他情况则无需做任何操作
            }

            // 响应设备数据
            ByteBuf byteBuf = buildHeartbeatResponsePackage(deviceIdByte, (byte) 0x06, new byte[]{0x00, 0x00});
            ctx.writeAndFlush(byteBuf);

        } else if (flag == 0x07) { //数据上报

            if (!checkCRCVal(dataByte)) {
                // 就算是crc校验不通过也要响应回数据，防止因为采集端不响应而脚标机因为没收到请求而重复发送请求bug
                ByteBuf byteBuf = buildHeartbeatResponsePackage(deviceIdByte, (byte) 0x07, new byte[]{0x02, dataReportCycle});
                ctx.writeAndFlush(byteBuf);
                return;
            }

            log.info(Thread.currentThread().getId() + "Option：贴标数据上报，设备编号：{}", deviceId);

            // 获取上报数据列表
            System.out.println(dataByte.length + "条");
            int dataCount = (dataByte.length - 13) / 58;
            List<LabelingRecord> labelingRecordList = getCollectionDataList(dataBuf, dataCount, barcodeMachine);
            //如果不为空
            if (!labelingRecordList.isEmpty()) {
                // 批量保存
                labelingRecordService.addbatch(labelingRecordList);
                //指令成功顺便存入redis
                addMessage(deviceId, "6");
                // 响应设备数据  上传成功0x00
                ByteBuf byteBuf = buildHeartbeatResponsePackage(deviceIdByte, (byte) 0x07, new byte[]{0x00, dataReportCycle});
                ctx.writeAndFlush(byteBuf);
            } else {
                // 响应设备数据   数据重传0x01
                ByteBuf byteBuf = buildHeartbeatResponsePackage(deviceIdByte, (byte) 0x07, new byte[]{0x01, dataReportCycle});
                ctx.writeAndFlush(byteBuf);
            }

//        } else if (flag == 0x08) {  // 获取禽类大类下的信息列表
//            //指令成功顺便存入redis
//            addMessage(deviceId, "7");
//            log.info(Thread.currentThread().getId() + "Option：获取禽类大类下的信息列表，设备编号：{}", deviceId);
//
//            // 获取禽类品种大类编号
//            byte poultryCategoryNumber = dataBuf.getByte(11);
//            log.debug(Thread.currentThread().getId() + "获取禽类品种大类编号" + poultryCategoryNumber);
//            Integer parentId = new Integer(poultryCategoryNumber);
//            // 获取大类编号下的子列表
//            List<PoultryCategory> poultryCategory = poultryCategoryService.getPoultryCategoryByParentId(parentId + "");
//            List<byte[]> poultryCategoryBytesList = parsePoultryCategoryTobytes(poultryCategoryNumber, poultryCategory);
//            // 创建禽类品种信息更新数据包
//            ByteBuf responseBuf = buildUpdateResponsePackage(deviceIdByte, (byte) 0x08, (short) -1, poultryCategoryBytesList);
//            ctx.writeAndFlush(responseBuf);
        } else if (flag == 0x09) {
            //开锁
            log.info(Thread.currentThread().getId() + "Option：开锁，设备编号：{}", deviceId);

            ByteBuf byteBuf;
            byte[] userIdByte = new byte[20];
            dataBuf.getBytes(11, userIdByte);
            String userId = ByteUtils.bytesToHexString(userIdByte);
            log.debug(Thread.currentThread().getId() + "开锁的用户二维码为" + userId);

            log.info(Thread.currentThread().getId() + "开锁成功");
            // 响应设备数据
            byteBuf = buildLoginResponsePackage(deviceIdByte, (byte) 0x09, new byte[]{0x00}, new byte[]{});
//            User user = userService.findById(userId);
//            //判断userId是否有idcard，有就说明被授权了，登录成功
//            if (user != null && user.getIdCard() != null && !user.getIdCard().equals("")) {
//                log.info(Thread.currentThread().getId() + "开锁成功");
//                // 响应设备数据
//                byteBuf = buildLoginResponsePackage(deviceIdByte, (byte) 0x09, new byte[]{0x00}, new byte[]{});
//            } else {
//                log.info(Thread.currentThread().getId() + "开锁失败");
//                byteBuf = buildLoginResponsePackage(deviceIdByte, (byte) 0x08, new byte[]{0x01}, new byte[]{});
//            }

            ctx.writeAndFlush(byteBuf);

        }
//        } else if (flag == 0x0a) {  // 设备获取固件包
//
//            log.info(Thread.currentThread().getId() + "Option：固件包更新，设备编号：{}", deviceId);
//
//            // 获取当前要下发的固件包信息
//            int totalProgramPackage = dataBuf.getShort(12);
//            int curProgramPackage = dataBuf.getShort(14);
//            byte[] curProgramPackageBytes = new byte[2];
//            dataBuf.getBytes(14, curProgramPackageBytes);
//
//            // 固件版本信息获取
//            ProgramVersion programVersion = programVersionService.getProgramVersion();
//            int firmwareVersion = programVersion.getFirmwareVersion();
//            byte firmwareVersionByte = (byte) firmwareVersion;
//
//            // 获取固件包更新
//            RandomAccessFile randomAccessFile = new RandomAccessFile(firmwareFilePath, "r");
//            randomAccessFile.seek((curProgramPackage - 1) * 1024);
//
//            // 开始发送文件
//            log.info(Thread.currentThread().getId() + "正在发送文件，total：{}，seq：{}", totalProgramPackage, curProgramPackage);
//
//            byte[] data = new byte[1024];
//            if (randomAccessFile.read(data) != -1) {
//                ByteBuf responseBuf = buildProgramUpdateResponsePackage(deviceIdByte, (byte) 0x0a, firmwareVersionByte, curProgramPackageBytes, data);
//                ctx.writeAndFlush(responseBuf);
//            }
//
//            if (curProgramPackage == totalProgramPackage) {
//                log.info(Thread.currentThread().getId() + "固件更新结束！设备编号：{}", deviceId);
//            }
//
//        }


    }


//    /**
//     * 数据读取完毕
//     *
//     * @param ctx
//     * @throws Exception
//     */
//    @Override
//    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//        // 将服务端要写的消息刷新到远程结点，并关闭channel
//        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
//    }


    /**
     * 异常处理
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error(Thread.currentThread().getId() + "服务端异常,通道已关闭...");

        // 关闭通道
        cause.printStackTrace();
        ctx.close();
    }


    /**
     * 解析采集的贴标数据集合  因为一次只查询一条
     *
     * @param dataBuf        数据内容
     * @param barcodeMachine 脚标机信息
     * @return List<LabelingRecord>
     */
    private List<LabelingRecord> getCollectionDataList(ByteBuf dataBuf, int dataCount, BarcodeMachine barcodeMachine) {
        List<LabelingRecord> labelingRecordList = new ArrayList<>(dataCount);
        int headLen = 11;
        System.out.println("一共上传" + dataCount + "条贴标数据");
        for (int i = 0; i < dataCount; i++) {
            LabelingRecord labelingRecord = new LabelingRecord();
            // 初始化贴标签的基本信息
            labelingRecord.setBarcodeMachineId(barcodeMachine.getNumber());
            labelingRecord.setStallsId(barcodeMachine.getStallsId());
            labelingRecord.setCreateDate(new Date());
            labelingRecord.setCreateBy(barcodeMachine.getCreateBy());

            labelingRecord.setId(idWorker.nextId() + "");

            // 溯源码信息
            byte[] barCodeBytes = new byte[24];
            dataBuf.getBytes(headLen + i * 58, barCodeBytes);
            String barCode = ByteUtils.bytesToASCIIString(barCodeBytes);
            //这里的溯源码需要做一个截取
            int index = barCode.indexOf("-");
            if (index != -1) {
                barCode = barCode.substring(0, index);
            }
            labelingRecord.setBarcodeNumber(barCode);
            log.info("溯源码id为：" + barCode);

            // 禽类父类id
            int poultryCategoryNumber = dataBuf.getInt(headLen + i * 58 + 24 + 2);
            labelingRecord.setPoultryCategoryId(poultryCategoryNumber + "");

            //家禽细类id
            byte[] bya = new byte[4];
            dataBuf.getBytes(headLen + i * 58 + 24 + 2, bya);
            labelingRecord.setPoultryChildId(Integer.parseInt(ByteUtils.bytesToHexString(bya).replaceAll(" ", ""), 16) + "");
            System.out.println("禽类子类的id为" + labelingRecord.getPoultryChildId());

            // 日期
            byte[] dateBytes = new byte[6];
            dataBuf.getBytes(headLen + i * 58 + 24 + 6 + 20, dateBytes);
            Date date = DateUtils.hexBytesToDate(dateBytes);
            labelingRecord.setLabelingDate(date);

            // 市场去向二维码
            byte[] salesTerminalBytes = new byte[20];
            dataBuf.getBytes(headLen + i * 58 + 24 + 6, salesTerminalBytes);
            String code = ByteUtils.bytesToASCIIString(salesTerminalBytes);

            //这里通过code去redis中获取value，值为  角标机id_委托订单id，进行切割
            String str = (String) redisUtil.get(code);
            System.out.println("订单二维码" + code);
            if (str == null) {
                log.error(Thread.currentThread().getId() + "去向市场二维码失效了" + code);
                break;
            }
            String[] split = str.split("_");
            String barcodeId = split[1];  //脚标机编号
            String entrustId = split[0];  //订单id

            //得到订单
            Entrust entrust = entrustService.findById(entrustId);
            //如果订单的脚标机编号为null，则加入编号，并且将正在贴标时间加入
            if (entrust.getBarcodeNumber() == null) {
                entrust.setBarcodeNumber(barcodeId);
                entrust.setReLabellingTime(new Date());
            }
            //将订单状态改为正在贴标
            entrust.setState(4);
            entrustService.updateEntrust(entrust);

            labelingRecord.setToMarkId(entrust.getToMarketId());
            labelingRecord.setEntrustId(entrustId);
            labelingRecordList.add(labelingRecord);
            log.info("上传贴标数据：" + labelingRecord.toString());

        }
        return labelingRecordList;
    }


    /**
     * 校验 [收到的数据计算出来的CRC值] 与 [数据包中的CRC值]
     *
     * @param data 原缓冲区数据
     * @return 校验结果
     */
    private boolean checkCRCVal(byte[] data) {

        //如果被检测数据长度超过256，就只处理前256
        //修改为大于800字节
        int len = 0;
//        if ((data.length - 4) > 256)
        if ((data.length - 4) > 800)
            //len = 256;
            len = 800;
        else
            len = data.length - 4;


        byte[] checkData = new byte[len];
        System.arraycopy(data, 2, checkData, 0, len);

        if (doCheckCRCVal(checkData, data)) {
            System.out.println("crc验证成功");
            return true;
        }
        System.out.println("crc验证失败");
        return false;
    }


    /**
     * 对比数据CRC结果
     *
     * @param crcCheckData 待校验的数据：帧长度+帧代号+地址段+数据段
     * @param data         原来的数据包。
     * @return 一致则true, 否则false
     */
    private boolean doCheckCRCVal(final byte[] crcCheckData, final byte[] data) {
        int total = data.length;
        // 获取CRC校验位数据
        String t1 = ByteUtils.byteToHex(data[total - 2]);
        String t2 = ByteUtils.byteToHex(data[total - 1]);

        if (t1.length() < 2) {
            t1 = "0" + t1;
        }
        if (t2.length() < 2) {
            t2 = "0" + t2;
        }
        String originCrc = t1 + t2;

        String crc = CRC16.getCRC(crcCheckData);
        if (crc.equalsIgnoreCase(originCrc)) {
            return true;
        }

        log.error(Thread.currentThread().getId() + "校验不通过,原CRC校验码：" + originCrc + ",当前计算CRC校验码：" + crc);
        return false;
    }


    /**
     * 校验经纬度，正确返回true，错误返回false
     *
     * @param barcodeMachine 设备barcodeMachine
     * @param longitude      经度
     * @param latitude       纬度
     * @return boolean
     */
    private boolean checkGPS(BarcodeMachine barcodeMachine, byte[] longitude, byte[] latitude) {
        double longitudeVal = GPSUtils.dmParseDd(longitude);
        double latitudeVal = GPSUtils.dmParseDd(latitude);

        log.debug(Thread.currentThread().getId() + "测试-----经度  " + longitudeVal);
        log.debug(Thread.currentThread().getId() + "测试-----纬度  " + latitudeVal);

        // 查询当前脚标机所在位置
        double srcLongitude = Double.parseDouble(barcodeMachine.getLongitude());
        double srcLatitude = Double.parseDouble(barcodeMachine.getLatitude());
        System.out.println(srcLongitude + "       " + srcLatitude);
        // 是否在2km以内
        if (GPSUtils.isInRange2km(srcLongitude, srcLatitude, longitudeVal, latitudeVal)) {
            log.debug(Thread.currentThread().getId() + "测试-----设备GPS位置正确");
            return true;
        }
        log.debug(Thread.currentThread().getId() + "测试-----设备GPS位置非法");
        return false;
    }


    /**
     * 是否有新的禽类品种更新
     *
     * @param slaughterHouseId 屠宰场id
     * @return boolean
     */
    private boolean hasNewPoultry(String slaughterHouseId) {
        String isupdateByMachineId = slaughterHousePoultryService.getIsupdateByMachineId(slaughterHouseId);
        if ("1".equals(isupdateByMachineId)) {
            // 修改信息更新状态为未更新
            slaughterHousePoultryService.setIsupdateByMachineId(slaughterHouseId);
            return true;
        }
        return false;
    }

    /**
     * 是否有新的禽类品种更新
     *
     * @param slaughterHouseId 屠宰场id
     * @return boolean
     * @author minxin
     */
    private boolean hasNewPoultryTimeVersion(String timeVersion, String slaughterHouseId) {
        String time = slaughterHousePoultryService.getTimeVersionByMachineId(slaughterHouseId);
        if (time == null)
            return true;
        return time.equals(timeVersion) ? false : true;
    }

    /**
     * 判断当前的设备是否有新的用户更新
     *
     * @param deviceId 设备编号
     * @return boolean
     */
    private boolean hasNewUser(String deviceId) {
        // 查询脚标机用户信息表
        String isupdate = barcodeMachineUserService.getIsupdateByMachineId(deviceId);
        if ("1".equals(isupdate)) {
            // 修改信息更新状态为未更新
            barcodeMachineUserService.setIsupdateByMachineId(deviceId);
            return true;
        }
        return false;
    }

    /**
     * 判断当前设备的时间版本编号是否一致
     *
     * @param timeVersion 时间版本编号
     * @param deviceId    设备编号
     * @author cmx
     */
    private boolean hasNewTimeVersion(String timeVersion, String deviceId) {
        // 查询脚标机用户信息表
        String time = barcodeMachineUserService.getTimeVersionByMachineId(deviceId);
        if (time == null)
            return true;
        //相等返回是否需要更新 true为需要
        return timeVersion.equals(time) ? false : true;
    }


    /**
     * 判断是否有固件进行更新
     *
     * @param firmwareVersion 设备端固件版本
     * @return boolean
     */
    private boolean hasNewFirmware(int firmwareVersion) {
        ProgramVersion programVersion = programVersionService.getProgramVersion();
        if (programVersion.getFirmwareVersion() == firmwareVersion) {
            return false;
        }
        return true;
    }


    /**
     * 将要更新的用户信息组装成字节数组信息
     *
     * @return List<byte       [       ]>
     * 用户信息集合：[用户级别 + 用户ID卡信息 + 预留信息]
     */
    private List<byte[]> parseUserInfoTobytes(String adminIdCard, String userIdCard, Object[] splitIdCard) throws UnsupportedEncodingException {
        List<byte[]> userdataList = new ArrayList<>();

        log.debug(Thread.currentThread().getId() + "adminIdCard    " + adminIdCard);
//        log.debug(Thread.currentThread().getId() + "userIdCard   " + userIdCard);
//        log.debug(Thread.currentThread().getId() + "maintenanceIdCard   " + Arrays.toString(splitIdCard));

        // 屠宰场管理员
        byte[] adminInfo = new byte[24];
        adminInfo[0] = 0x02;
//        byte[] adminIdCardBytes = ByteUtils.longToBytes(Long.parseLong(adminIdCard));
        byte[] adminIdCardBytes = ByteUtils.hexStringToBytes(ByteUtils.stringToHexStr(adminIdCard));
        for (int i = adminIdCardBytes.length - 1, j = 20; i >= 0; i--, j--) {
            adminInfo[j] = adminIdCardBytes[i];
        }
        userdataList.add(adminInfo);
        return userdataList;

        // 屠宰场维护人员
//        for (Object idCard : splitIdCard) {
//            byte[] maintenanceInfo = new byte[24];
//            maintenanceInfo[0] = 0x01;
////            byte[] maintenanceIdCardBytes = ByteUtils.longToBytes(Long.parseLong(idCard));
////            byte[] maintenanceIdCardBytes = idCard.getBytes();
//            byte[] maintenanceIdCardBytes = ByteUtils.hexStringToBytes(ByteUtils.stringToHexStr((String)idCard));
//            for (int i = maintenanceIdCardBytes.length - 1, j = 20; i >= 0; i--, j--) {
//                maintenanceInfo[j] = maintenanceIdCardBytes[i];
//            }
//            userdataList.add(maintenanceInfo);
//        }

        // 普通用户
//        byte[] userInfo = new byte[24];
//        userInfo[0] = 0x02;
////        byte[] userIdCardBytes = ByteUtils.longToBytes(Long.parseLong(userIdCard));
////        byte[] userIdCardBytes = userIdCard.getBytes();
//        byte[] userIdCardBytes = ByteUtils.hexStringToBytes(ByteUtils.stringToHexStr(userIdCard));
//        for (int i = userIdCardBytes.length - 1, j = 20; i >= 0; i--, j--) {
//            userInfo[j] = userIdCardBytes[i];
//        }
//        userdataList.add(userInfo);

    }


    /**
     * 将要更新的禽类品种信息组装成字节数组信息
     * 禽类品种大类编号：
     * 0x00鸡，0x01 鸭， 0x02 鹅 ，0x03 其他
     *
     * @param poultryCategoryList 禽类品种集合
     * @return List<byte   [   ]>
     */
    private List<byte[]> parsePoultryCategoryTobytes(List<PoultryCategory> poultryCategoryList) {

        List<byte[]> poultryCategoryBytesList = new ArrayList<>();

        for (PoultryCategory poultryCategory : poultryCategoryList) {

            if ("0".equals(poultryCategory.getParentId())) {  // 大类-鸡
                addpoultryCategoryBytes(poultryCategoryList, poultryCategoryBytesList, (byte) 0x00, poultryCategory);

            } else if ("1".equals(poultryCategory.getParentId())) {   // 大类-鸭
                addpoultryCategoryBytes(poultryCategoryList, poultryCategoryBytesList, (byte) 0x01, poultryCategory);

            } else if ("2".equals(poultryCategory.getParentId())) {   // 大类-鹅
                addpoultryCategoryBytes(poultryCategoryList, poultryCategoryBytesList, (byte) 0x02, poultryCategory);

            } else if ("3".equals(poultryCategory.getParentId())) {   // 大类-其他
                addpoultryCategoryBytes(poultryCategoryList, poultryCategoryBytesList, (byte) 0x03, poultryCategory);
            }

        }

        return poultryCategoryBytesList;
    }


    /**
     * 将指定要更新的大类品种下的子类信息信息组装成字节数组信息
     * 禽类品种大类编号：
     * 0x00鸡，0x01 鸭， 0x02 鹅 ，0x03 其他
     *
     * @param number              大类编号
     * @param poultryCategoryList 禽类品种集合
     * @return List<byte   [   ]>
     */
    private List<byte[]> parsePoultryCategoryTobytes(byte number, List<PoultryCategory> poultryCategoryList) {

        List<byte[]> poultryCategoryBytesList = new ArrayList<>();
        int num = number;
        String numStr = num + "";

        for (PoultryCategory poultryCategory : poultryCategoryList) {
            if (numStr.equals(poultryCategory.getParentId())) {
                addpoultryCategoryBytes(poultryCategoryList, poultryCategoryBytesList, number, poultryCategory);
                break;
            }
        }
        return poultryCategoryBytesList;
    }


    /**
     * 将禽类信息添加到对应的字节数组集合中去
     *
     * @param poultryCategoryList      原禽类信息集合
     * @param poultryCategoryBytesList 要组装的禽类信息的集合，字节数组集合
     * @param parentNumber             大类品种id
     */
    private void addpoultryCategoryBytes(List<PoultryCategory> poultryCategoryList, List<byte[]> poultryCategoryBytesList, byte parentNumber, PoultryCategory poultryCategory) {
        List<PoultryCategory> subCategory = getSubCategory(poultryCategoryList, poultryCategory.getParentId());

        for (PoultryCategory sub : subCategory) {

            if (!sub.getName().equals(poultryCategory.getName())) {
                continue;
            }

            byte[] poultryCategoryByte = new byte[16];
            poultryCategoryByte[0] = 0x00;
            poultryCategoryByte[1] = parentNumber;

            byte[] subNumber = ByteUtils.intToBytes(Integer.parseInt(sub.getNumber()));
            byte[] subNameGbk = sub.getName().getBytes(Charset.forName("GBK"));

            System.arraycopy(subNumber, 0, poultryCategoryByte, 2, subNumber.length);
            System.arraycopy(subNameGbk, 0, poultryCategoryByte, 6, subNameGbk.length);
            //家禽名称：5个中文字符(10 Byte)，每个中文字GBK编号为2 Byte，家禽名称不足10 Byte的，加0xFF FF补齐到10 Byte;
            if (subNameGbk.length < 10) {
                for (int i = 0; i < (10 - subNameGbk.length); i++) {
                    poultryCategoryByte[15 - i] = (byte) 0xff;
                }
            }
            poultryCategoryBytesList.add(poultryCategoryByte);
            break;
        }
    }

    /**
     * 获取大类品种下的小类信息
     *
     * @param poultryCategoryList 所有禽类品种信息列表
     * @param parentId            父级编号
     */
    private List<PoultryCategory> getSubCategory(List<PoultryCategory> poultryCategoryList, String parentId) {
        List<PoultryCategory> list = new ArrayList<>();

        for (PoultryCategory poultryCategory : poultryCategoryList) {
            if (poultryCategory.getParentId().equals(parentId)) {
                list.add(poultryCategory);
            }
        }

        return list;
    }


    /**
     * 构建设备登录响应设备的报文
     *
     * @param deviceId  设备编号
     * @param frameCode 帧代号
     * @param flag      标志位
     * @param data      响应数据内容
     * @return ByteBuf
     */
    private ByteBuf buildLoginResponsePackage(byte[] deviceId, byte frameCode, byte[] flag, byte[] data) {
        // 总长度
        int headLen = 11;   // 帧头 + 帧长度 + 帧代号 + 设备编号
        int footLen = 4;    // CRC校验 + 帧尾
        int len = headLen + 2 + data.length + footLen;
        byte[] dataByte = new byte[len];
        byte[] checkData = new byte[len - 6];
        ByteBuf buffer = Unpooled.buffer(len);

        // 响应数据
        buffer.writeBytes(new byte[]{(byte) 0x88, (byte) 0xFB});  // 帧头
        buffer.writeShort((len - 6));  // 帧长度（[帧代号]->[CRC]的字节数） 2个字节
        buffer.writeByte(frameCode);    // 帧代号
        buffer.writeBytes(deviceId);    // 设备编号
        buffer.writeBytes(flag);    // 标志位
        buffer.writeBytes(data);    // 数据内容

        // 获取CRC校验码
        buffer.getBytes(buffer.readerIndex(), dataByte);
        System.arraycopy(dataByte, 2, checkData, 0, checkData.length);
        byte[] crcBytes = CRC16.getCrcBytes(checkData);
        buffer.writeBytes(crcBytes);   // CRC校验数据
        buffer.writeBytes(new byte[]{(byte) 0xFC, (byte) 0xFC});    // 帧尾

        return buffer;
    }


    /**
     * 构建信息更新响应设备的报文
     *
     * @param deviceId      设备编号
     * @param frameCode     帧代号
     * @param updateVersion 更新版本
     * @param dataList      响应数据内容集合
     * @return ByteBuf
     * 00 1b 08 03 03 03 03 03 03 00 00 00 00 03 e9 c7 e5 d4 b6 bc a6 ff ff ff ff 00 00
     * 00 1b 08 03 03 03 03 03 03 00 00 00 00 03 e9 c7 e5 d4 b6 bc a6 ff ff ff ff
     */
    private ByteBuf buildUpdateResponsePackage(byte[] deviceId, byte frameCode, short updateVersion, List<byte[]> dataList) {
        if (dataList.size() == 0)
            return Unpooled.EMPTY_BUFFER;

        // 总长度
        int headLen = 11;   // 帧头 + 帧长度 + 帧代号 + 设备编号  2+2+1+6
        int footLen = 4;    // CRC校验 + 帧尾  2+2
        int len = headLen + 2 + dataList.size() * dataList.get(0).length + footLen;  //这里的2是用户时间版本的长度
        //如果是第八条指令的话len需要减去二，因为没有长度为二的时间序列长度
        if (frameCode == (byte) 0x08) {
            len -= 2;
        }

        byte[] dataByte = new byte[len];
        byte[] checkData = new byte[len - 6];

        ByteBuf buffer = Unpooled.buffer(len);

        // 响应数据
        buffer.writeBytes(new byte[]{(byte) 0x88, (byte) 0xFB});  // 帧头
        buffer.writeShort((len - 6));  // 帧长度（[帧代号]->[CRC]的字节数） 2个字节
        buffer.writeByte(frameCode);    // 帧代号          1字节
        buffer.writeBytes(deviceId);    // 设备编号     6字节
        if (updateVersion != -1)
            buffer.writeShort(updateVersion);    // 信息时间版本号  2字节

        // 更新信息数据内容
        for (byte[] dataInfo : dataList) {
            buffer.writeBytes(dataInfo);
        }

        // 获取CRC校验码  0,
        buffer.getBytes(buffer.readerIndex(), dataByte);

//        System.out.println(ByteUtils.bytesToHexString(dataList));
        // System.out.println("dataByte为：" + ByteUtils.bytesToHexString(dataByte));

        System.arraycopy(dataByte, 2, checkData, 0, checkData.length);

        //  System.out.println("checkData为：" + ByteUtils.bytesToHexString(checkData));

        byte[] crcBytes = CRC16.getCrcBytes(checkData);

        System.out.printf("crc校验码为：");
        for (byte a : crcBytes
                ) {
            System.out.printf(ByteUtils.byteToHex(a) + " ");
        }

        buffer.writeBytes(crcBytes);   // CRC校验数据
        buffer.writeBytes(new byte[]{(byte) 0xFC, (byte) 0xFC});    // 帧尾

        return buffer;
    }


    /**
     * 构建心跳数据响应设备的报文
     *
     * @param deviceId  设备编号
     * @param frameCode 帧代号
     * @param data      响应数据内容集合
     * @return ByteBuf
     */
    private ByteBuf buildHeartbeatResponsePackage(byte[] deviceId, byte frameCode, byte[] data) {

        // 总长度
        int headLen = 11;   // 帧头 + 帧长度 + 帧代号 + 设备编号
        int footLen = 4;    // CRC校验 + 帧尾
        int len = headLen + data.length + footLen;
        byte[] dataByte = new byte[len];
        byte[] checkData = new byte[len - 6];
        ByteBuf buffer = Unpooled.buffer(len);

        // 响应数据
        buffer.writeBytes(new byte[]{(byte) 0x88, (byte) 0xFB});  // 帧头
        buffer.writeShort((len - 6));    // 帧长度（[帧代号]->[CRC]的字节数） 2个字节
        buffer.writeByte(frameCode);    // 帧代号
        buffer.writeBytes(deviceId);    // 设备编号
        buffer.writeBytes(data);    // 数据

        // 获取CRC校验码
        buffer.getBytes(buffer.readerIndex(), dataByte);
        System.arraycopy(dataByte, 2, checkData, 0, checkData.length);
        byte[] crcBytes = CRC16.getCrcBytes(checkData);
        buffer.writeBytes(crcBytes);   // CRC校验数据
        buffer.writeBytes(new byte[]{(byte) 0xFC, (byte) 0xFC});    // 帧尾

        return buffer;
    }


    /**
     * 构建程序更新信息响应数据包
     *
     * @param deviceId             设备编号
     * @param frameCode            帧代号
     * @param firmwareVersionBytes 固件版本号
     * @param packageLengthBytes   固件包的大小 KB为单位
     * @param fileSizeBytes        固件长度
     * @return ByteBuf
     */
    private ByteBuf buildProgramUpdateResponsePackage(byte[] deviceId, byte frameCode, byte firmwareVersionBytes, byte[] packageLengthBytes, byte[] fileSizeBytes) {
        // 总长度
        int headLen = 11;   // 帧头 + 帧长度 + 帧代号 + 设备编号
        int footLen = 4;    // CRC校验 + 帧尾
        int len = headLen + 1 + packageLengthBytes.length + fileSizeBytes.length + footLen;
        byte[] dataByte = new byte[len];
        byte[] checkData = new byte[len - 6];
        ByteBuf buffer = Unpooled.buffer(len);

        // 响应数据
        buffer.writeBytes(new byte[]{(byte) 0x88, (byte) 0xFB});  // 帧头
        buffer.writeShort((len - 6));  // 帧长度（[帧代号]->[CRC]的字节数） 2个字节
        buffer.writeByte(frameCode);    // 帧代号
        buffer.writeBytes(deviceId);    // 设备编号
        buffer.writeByte(firmwareVersionBytes); // 固件版本
        buffer.writeBytes(packageLengthBytes);  // 固件包大小
        buffer.writeBytes(fileSizeBytes);   // 数据 F2B0

        // 获取CRC校验码
        buffer.getBytes(buffer.readerIndex(), dataByte);
        System.arraycopy(dataByte, 2, checkData, 0, checkData.length);
        byte[] crcBytes = CRC16.getCrcBytes(checkData);
        buffer.writeBytes(crcBytes);   // CRC校验数据
        buffer.writeBytes(new byte[]{(byte) 0xFC, (byte) 0xFC});    // 帧尾

        return buffer;
    }

    /**
     * 将每一条指令插入到redis，提供数据给监控模块
     *
     * @param number 脚标机id
     * @param order  指令序列  0-6
     */
    public void addMessage(String number, String order) {
        //设置时间
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = new Date();
        String time = simpleDateFormat.format(date);

        Message message = new Message();
        message.setDate(time);
        message.setOrder(order);
        redisUtil.lSet(number, message);
    }

}
//88 fb 00 0c 06 03 03 03 03 03 03 23 21 b5 F2 B0 fc fc